﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;

using SharedGenomics.Core;
using SharedGenomics.Core.Collections;
using SharedGenomics.Annotations;

namespace SharedGenomics.Workbench.Annotations
{
    /// <summary>
    /// Implementation of <see cref="IAnnotationPlan"/> that is created during initialisation and then never changes. If an annotation fails then no attempt is 
    /// made to adapt the plan and any dependent annotations will fail immediately.
    /// </summary>
    public class StaticAnnotationPlan : IAnnotationPlan
    {
        private readonly Tree<AnnotationKey> _plan;
        private Queue<TreeNode<AnnotationKey>> _pendingRequestNodes;
        private List<TreeNode<AnnotationKey>> _runningRequestNodes;

        private readonly object _pendingLock = new object();    //lock object for pending queue
        private readonly object _runningLock = new object();    //lock object for running queue

        public StaticAnnotationPlan(Tree<AnnotationKey> planTree)
        {
            planTree.ThrowIfNull("planTree");

            this._plan = planTree;
            this._pendingRequestNodes = new Queue<TreeNode<AnnotationKey>>();
            this._runningRequestNodes = new List<TreeNode<AnnotationKey>>();

            //add all immediate children of the root node to the pending queue
            this.QueueChildrenPending(planTree.Root);
        }

        public IEnumerable<AnnotationKey> GetPendingRequests()
        {
            TreeNode<AnnotationKey>[] pending;
            
            lock (this._pendingLock)
            {
                //remove requests from the pending queue and add to the running collection
                pending = Enumerable.Range(0, this._pendingRequestNodes.Count).Select(i => this._pendingRequestNodes.Dequeue()).ToArray();
            }

            lock (this._runningLock)
            {
                this._runningRequestNodes.AddRange(pending);
            }

            return pending.Select(node => node.Value);
        }

        public void NotifyRequestFinished(AnnotationKey requestKey)
        {
            requestKey.ThrowIfNull("requestKey");

            //remove corresponding node from the running queue
            var keyNode = this.RemoveKeyFromRunningCollection(requestKey);

            //unblock any dependent requests by adding them to the pending queue
            if (keyNode != null)
            {
                this.QueueChildrenPending(keyNode);
            }
        }

        public void NotifyRequestFailed(AnnotationKey requestKey)
        {
            requestKey.ThrowIfNull("requestKey");

            //remove corresponding node from the running queue
            var failedNode = this.RemoveKeyFromRunningCollection(requestKey);

            if (failedNode != null)
            {
                //get all keys in the subtree for the failed node and notify any listeners these requests have failed
                var failedKeys = failedNode.SubTreeNodes.Select(node => node.Value).ToList();

                //raise requests failed event
                this.RequestsFailed.Raise(this, new AnnotationPlanRequestsFailedEventArgs(failedKeys));
            }
        }

        public IAnnotationPlan Clone()
        {
            return new StaticAnnotationPlan(this._plan);
        }

        public bool IsComplete
        {
            get
            {
                lock (this._pendingLock)
                {
                    lock (this._runningLock)
                    {
                        return  this._pendingRequestNodes.Count == 0 &&
                                this._runningRequestNodes.Count == 0;
                    }
                }
            }
        }

        public event EventHandler<AnnotationPlanRequestsFailedEventArgs> RequestsFailed;

        public event EventHandler RequestsAvailable;

        private TreeNode<AnnotationKey> RemoveKeyFromRunningCollection(AnnotationKey key)
        {
            Debug.Assert(key != null);

            TreeNode<AnnotationKey> removedKeyNode = null;

            lock (this._runningLock)
            {
                int index = this._runningRequestNodes.FindIndex(node => node.ValueEquals(key));
                if (index != -1)
                {
                    removedKeyNode = this._runningRequestNodes[index];
                    this._runningRequestNodes.RemoveAt(index);
                }
            }

            return removedKeyNode;
        }

        private void QueueChildrenPending(TreeNode<AnnotationKey> node)
        {
            Debug.Assert(node != null);

            lock (this._pendingLock)
            {
                foreach (var childNode in node.Children)
                {
                    this._pendingRequestNodes.Enqueue(childNode);
                }

                if (this._pendingRequestNodes.Count > 0)
                {
                    this.RequestsAvailable.Raise(this);
                }
            }
        }
    }
}
